
#include "vm/opcodes.hpp"

#include "rep/running.hpp"

#include "vm/builtins.hpp"

using namespace rep;

namespace vm{
  Continuation handle_noop(Cons* stack, Slot* env, Instruction pc, Continuation standard_next){
    return standard_next;
  }

  Continuation handle_builtin(Cons* stack, Slot* env, Instruction pc, Continuation standard_next){
    switch(pc.arg.kind){
    case(UNSIGNED):
      return Builtin::builtins[pc.arg.un]->execute(stack, env, pc, standard_next);
      break;
    default:
      ///XXX
      return standard_next;
    }
  }

  Continuation handle_call(Cons* stack, Slot* env, Instruction pc, Continuation standard_next){
    assert(pc.numpop >= 1);
    Slot fun;

    stack = stack->pop(&fun);
    if(fun.tag != Slot::LAMBDA){
      //No good! XXX
      return standard_next;
    }
    //allocate the frame for the funcall.
    /*Slot* frame = FrameHelper::alloc(fun.proc->scope());
    //store the return continuation
    ContinuationHelper::assign(FrameHelper::immDirectLookup(frame, Slot::RETURN), 
                               new (GC) Continuation(standard_next));
    stack = fun.proc->scope()->packArgs(stack, frame, pc.numpop-1);

    return Continuation(stack, frame, fun.proc->body(), 0); */

    return fun.proc->apply(stack, env, pc.numpop-1,
                           new (GC) Continuation(standard_next),
                           FrameHelper::except(env),
                           FrameHelper::yield(env), 
                           NULL, NULL, NULL);//erases information
                                             //about break, etc
                                             //for a standard
                                             //call.
  }

  Continuation handle_callspecial(Cons* stack, Slot* env, Instruction pc, Continuation standard_next){
    assert(pc.numpop >= 1);
    Slot fun;
    Slot arg;
    stack = stack->pop(&fun);
    if(fun.tag != Slot::LAMBDA){
      //No good! XXX
      return standard_next;
    }
    //allocate the frame for the funcall.
    Slot* frame = FrameHelper::alloc(fun.proc->scope());
    //store the bindings list is responsible for the return continuation.

    if(fun.proc->scope()->bindings==NULL){
      //no standard arguments.
    }else{
      //put in the args
      for(int popped = 0; popped < pc.numpop-1; popped++){
        stack = stack->pop(&arg);
        *FrameHelper::immDirectLookup(frame, fun.proc->scope()->bindings[popped]) = arg;
      }
    }
    return Continuation(stack, frame, fun.proc->body(), 0); 
  }

  Continuation handle_go_cont(Cons* stack, Slot* env, Instruction pc, Continuation standard_next){
    Slot cont_slot;
    Slot arg;
    stack = stack->pop(&cont_slot);
    assert(cont_slot.tag == Slot::CONTINUE);
    //transfer one value between stacks.
    stack = stack->pop(&arg);
    cont_slot.cont->stack = cont_slot.cont->stack->push(arg);
    return *cont_slot.cont;
  }

  /**
   * Pushes a continuation onto the stack referring to the current pc
   * plus whatever unsigned integer is in ref.
   */
  Continuation handle_make_cont(Cons* stack, Slot* env, Instruction pc, Continuation standard_next){
    Slot temp;
    ContinuationHelper::assign(&temp, new (GC) Continuation(standard_next));
    assert(pc.arg.kind == UNSIGNED);
    temp.cont->offset += pc.arg.un;
    return standard_next.push(temp);
  }

  Continuation handle_store(Cons* stack, Slot* env, Instruction pc, Continuation standard_next){
    Slot* where = pc.arg.dereference(env);
    if(where){
      stack->pop(where);
    }
    standard_next.stack = stack;
    return standard_next;
  }

  Continuation handle_load(Cons* stack, Slot* env, Instruction pc, Continuation standard_next){
    Slot val = pc.arg.value(env);
    return standard_next.push(val);
  }

  Continuation handle_end(Cons* stack, Slot* env, Instruction pc, Continuation standard_next){
    //XXX
    return standard_next;
  }

};
